package utilities;

import object.Tile;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;

/**
 *
 * Class managing the tiles objects and generel build of the levels
 */
public class TileManager
{
    private final static String IMAGE_DIR="Images/";
    private final static int MAX_TILE_LINES=20;
    private final static double MOVE_FACTOR=0.2;

    private int width, height;
    private int pWidth, pHeight;
    private int imWidth, imHeight;
    private int numberOfColumns, numberOfRows;
    private int moveSize;
    private int xMapHead;

    private boolean isMovingRight;
    private boolean isMovingLeft;

    private ArrayList tileList;
    private ArrayList tileImages=null;
    private ArrayList[] columnTiles;
    private ImageLoader imLoader;

    //Creates a new TileManager
    public TileManager(int w,int h,String filename,ImageLoader il)
    {
        pWidth=w;
        pHeight=h;
        imLoader=il;

        tileList=new ArrayList();
        loadTileFile(filename);
        initTilesInfo();
        createColumns();

        moveSize=(int)(imWidth*MOVE_FACTOR);
        if (moveSize==0)
        {
            System.out.println("moveSize cannot be 0, setting it to 1");
            moveSize = 1;
        }

        isMovingRight=false;
        isMovingLeft=false;
        xMapHead = 0;
    }

    //loads the file containing the tile information and starts loading the specified images
    private void loadTileFile(String filename)
    {
        String path=IMAGE_DIR + filename;
        System.out.println("Reading bricks file: "+path);

        int numberOfStripImages=-1;
        int numberOfBricksLines=0;
        try
        {
            InputStream in=this.getClass().getResourceAsStream(path);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String line;
            char ch;
            while((line = br.readLine())!= null)
            {
                if (line.length()==0)
                    continue;
                if (line.startsWith("//"))
                    continue;
                ch=Character.toLowerCase(line.charAt(0));
                if (ch=='s')
                    numberOfStripImages=getStripImages(line);
                else
                {
                    if (numberOfBricksLines>MAX_TILE_LINES)
                        System.out.println("Max reached, skipping tile line: "+line);
                    else
                        if (numberOfStripImages==-1)
                            System.out.println("No strip image, skipping tile line: " + line);
                        else
                        {
                            storeTiles(line,numberOfBricksLines,numberOfStripImages);
                            numberOfBricksLines++;
                        }
                }
            }
            br.close();
        }
        catch (IOException e)
        {
            System.out.println("Error reading file: "+path);
            System.exit(1);
        }
    }

    private String getPrefix(String filename)
    {
        int posn;
        if ((posn=filename.lastIndexOf(".")) == -1)
        {
            System.out.println("No prefix found for filename: "+filename);
            return filename;
        }
        else
            return filename.substring(0,posn);
    }

    private int getStripImages(String line)
    {
        StringTokenizer tokens=new StringTokenizer(line);
        if (tokens.countTokens()!=3)
        {
            System.out.println("Wrong no. of arguments for " + line);
            return -1;
        }
        else
        {
            tokens.nextToken();
            System.out.print("Bricks strip: ");

            String filename=tokens.nextToken();
            int number=-1;

            try
            {
                number=Integer.parseInt(tokens.nextToken());
                imLoader.loadStripImages(filename,number);
                tileImages=imLoader.getImages(getPrefix(filename));
            }

            catch(Exception e)
            {
                System.out.println("Number is incorrect for " + line);
            }

            return number;
        }
    }

    //Stores the tiles' imageIDs in a list
    private void storeTiles(String line, int lineNo, int numberOfImages)
    {
        int imageID;
        for (int i=0;i<line.length();i++)
        {
            char ch=line.charAt(i);
            if (ch == ' ')
                continue;
            if (Character.isDigit(ch))
            {
                imageID=ch-'0';
                if (imageID>=numberOfImages)
                    System.out.println("Image ID "+imageID+"out of range");
                else
                tileList.add(new Tile(imageID,i,lineNo));
            }
            else
                System.out.println("Tile char " + ch + " is not a digit");
        }
    }

    private void initTilesInfo()

    {
        if (tileImages==null)
        {
            System.out.println("No bricks images were loaded");
            System.exit(1);
        }
        if (tileList.size()==0)
        {
            System.out.println("No bricks map were loaded");
            System.exit(1);
        }

        BufferedImage bufImage=(BufferedImage)tileImages.get(0);
        imWidth=bufImage.getWidth();
        imHeight=bufImage.getHeight();

        findNumberOfTiles();
        calcMapDimensions();
        //checkForGaps();

        addTileDetails();
    }

    //finds the maximum number of tiles along both axes
    private void findNumberOfTiles()
    {
        Tile tile;
        numberOfColumns=0;
        numberOfRows=0;
        for (int i=0;i<tileList.size();i++)
        {
            tile=(Tile)tileList.get(i);
            if (numberOfColumns<tile.getMapX())
                numberOfColumns=tile.getMapX();
            if (numberOfRows<tile.getMapY())
                numberOfRows=tile.getMapY();
        }
        numberOfColumns++;
        numberOfRows++;
    }

    //converts the number of tiles into pixel dimensions
    private void calcMapDimensions()  
    {
        width=imWidth*numberOfColumns;
        height=imHeight*numberOfRows;

        if (width<pWidth)
        {
            System.out.println("Tile map is less wide than the panel");
            System.exit(0);
        }
    }

    //adds the image references to the tiles
    private void addTileDetails()
    {
        Tile tile;
        BufferedImage bufIm;
        for (int i=0;i<tileList.size();i++)
        {
            tile=(Tile)tileList.get(i);
            bufIm=(BufferedImage)tileImages.get(tile.getImageID());
            tile.setImage(bufIm);
            tile.setLocY(pHeight,numberOfRows);
        }
    }

    private void createColumns()
    {
        columnTiles=new ArrayList[numberOfColumns];
        for (int i=0;i<numberOfColumns;i++)
            columnTiles[i]=new ArrayList();

        Tile tile;
        for (int i=0;i<tileList.size();i++)
        {
            tile=(Tile)tileList.get(i);
            columnTiles[tile.getMapX()].add(tile);
        }
    }

    public int findFloor(int x)
    //finds the height of the floor for the initial position of the Player
    {
        int xMap=(int)(x/imWidth);
        int locY=pHeight;
        ArrayList column=columnTiles[xMap];
        Tile tile;
        for (int i=0;i<column.size();i++)
        {
            tile=(Tile)column.get(i);
            if (tile.getLocY()<locY)
                locY=tile.getLocY();
        }
        
        return locY;
    }

    //converts world coordinates to a map index tuple
    private Point worldToMapCoord(int x,int y)
    {
        x=x%width;
        y=y-(pHeight-height);
        if (x<0)
            x+=width;
        int mapX=(int)(x/imWidth);
        int mapY=(int)(y/imHeight);

        if (y<0)
            mapY=mapY-1;

        return new Point(mapX,mapY);
    }

    //check the Tile
    public boolean checkTile(int x,int y)
    {
        Point mapCoord=worldToMapCoord(x,y);
        ArrayList column=columnTiles[mapCoord.x];

        Tile tile;
        for (int i=0;i<column.size();i++)
        {
            tile=(Tile)column.get(i);
            if (mapCoord.y==tile.getMapY())
                return true;
        }

        return false;
    }

    public int checkTileDown(int x,int y,int step)
    //check if there's a tile beneath the Sprite
    {
        if (checkTile(x,y))
        {
            int yMapWorld=y-(pHeight-height);
            int mapY=(int)(yMapWorld/imHeight);
            int topOffset=yMapWorld-(mapY*imHeight);
            int smallStep=step-topOffset;
            return smallStep;
        }
        return step;
    }

    public int checkTileUp(int x,int y,int step)
    //check if there's a tile above the Sprite
    {
        if (checkTile(x,y))
        {
            int yMapWorld=y-(pHeight-height);
            int mapY=(int)(yMapWorld/imHeight);
            int topOffset=yMapWorld-(mapY*imHeight);
            int smallStep=step-(imHeight-topOffset);
            return smallStep;
        }
        return step;
    }

    public int getTileHeight()
    {
        return imHeight;
    }
    
    public int getMoveSize()
    {
        return moveSize;
    }
    
    public void moveLeft()
    { 
        isMovingLeft=true;
        isMovingRight=false;
    }

    public void moveRight()
    {
        isMovingLeft=false;
        isMovingRight=true;
    }

    public void stayStill()
    {
        isMovingLeft=false;
        isMovingRight=false;
    }

    public void update()
    {
        if (isMovingRight)
            xMapHead=(xMapHead+moveSize)%width;
        else
            if (isMovingLeft)
                xMapHead=(xMapHead-moveSize)%width;
    }

    private void drawTiles(Graphics g, int start, int end, int xTile)
    {
        int xMap=xTile/imWidth;
        ArrayList column;
        Tile tile;
        for (int i=start;i<end;i+=imWidth)
        {
            column=columnTiles[xMap];
            for (int j=0;j<column.size();j++)
            {
                tile=(Tile)column.get(j);
                tile.display(g,i);
            }
            xMap++;
        }
    }

    public void display(Graphics g)
    {
        int tCoord=(int)(xMapHead/imWidth)*imWidth;
        int offset;
        if (tCoord>=0)
            offset=xMapHead-tCoord;
        else
            offset=tCoord-xMapHead;

        if ((tCoord>=0)&&(tCoord <pWidth))
        {
            drawTiles(g,0-(imWidth-offset),xMapHead,width-tCoord-imWidth);
            drawTiles(g,xMapHead,pWidth,0);
        }
        else
            if (tCoord >= pWidth)
                drawTiles(g,0-(imWidth-offset),pWidth,width-tCoord-imWidth);
            else
                if((tCoord<0)&&(tCoord>=pWidth-width+imWidth))
                    drawTiles(g,0-offset,pWidth,-tCoord);
                else
                    if (tCoord<pWidth-width+imWidth)
                    {
                        drawTiles(g,0-offset,width+xMapHead,-tCoord);
                        drawTiles(g,width+xMapHead,pWidth,0);
                    }
    }


}
